using System;
using System.Collections;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Windows.Forms;

namespace SiRtEt
{
	/// <summary>
	/// Summary description for TheWorld.
	/// </summary>
	public class TheWorld : System.Windows.Forms.Control
	{
		#region Member Variables

        // Number of rows and columns in the game world
        //------------------------------
        /// <summary>
        /// The rows in the grid
        /// </summary>
		protected int _Rows;
        /// <summary>
        /// The columns in the grid
        /// </summary>
        protected int _Columns;
        //------------------------------
        
        /// <summary>
        /// Length of a side of the Unit Square
        /// </summary>
        protected int _UnitSquareWidth;
        ///<summary>
        /// All the unit squares that make the game world			
        /// </summary>
        public UnitSquare[] TheSquares;

        ///<summary>
        /// The current block being dealt with
        /// </summary>
        protected BlockBase TheBlock;
        ///<summary>
        /// The squares that need to be painted in the next paint cycle
        /// </summary>
        public ArrayList SquaresToInvalidate;

        // The score and the lines cleard till now
		public int Score,Lines,Level;
        // Flags to keep track of status
		public bool GameStart,GameEnd,GamePaused;
        
        // Index for the Blocks
        public int NextBlock;

        // A flag to check if a new block is required
		public bool NewBlockReqd;
        // The world time ticks manager
		protected Timer TheTimer;

        // The string to display the game status
		public string StatusString;

        #endregion

        #region Events
        /// <summary>
        /// Occurs when a new game starts
        /// </summary>
		public event EventHandler StartGame;
        /// <summary>
        /// Occurs when the next block in queus is ready for display
        /// </summary>
		public event EventHandler NextBlockMade;
        /// <summary>
        /// Occurs when a new block is initiated
        /// </summary>
		public event EventHandler NewBlockMade;
        /// <summary>
        /// Occurs when the game is over
        /// </summary>
		public event EventHandler EndGame;
        #endregion

        #region Properties
        /// <summary>
        /// Gets the number of rows in the world
        /// </summary>
        public int Rows { get { return _Rows; } }
        /// <summary>
        /// Gets the number of columns in the world
        /// </summary>
        public int Columns { get { return _Columns; } }
        /// <summary>
        /// Gets the width of a unit square for the world
        /// </summary>
        public int UnitSquareWidth { get { return _UnitSquareWidth; } }
        #endregion

        #region Constructor
        /// <summary>
		/// Constructor : Initializes all the various elements in the world so that they work well
		/// together .. well oiled and well greased
		/// </summary>
		public TheWorld()
		{
			// This call is required by the Windows.Forms Form Designer.
			InitializeComponent();

			ResizeRedraw = true;
			BackColor = SystemColors.Window;

			StatusString = "Start a New Game";

            // Setting up the grid rows and columns and the unit square width
            //----------------------------
            _Rows = 20;
            _Columns = 10;
            _UnitSquareWidth = 20;
            //----------------------------

			// Indicate that we still are to start (or end) the game
			GameStart = GameEnd = false;
            //.. and thus dont need a block yet
			NewBlockReqd = false;

			// Setting up the list of squares that need to be painted
			SquaresToInvalidate = new ArrayList();

			// Setting up the world timer
            //--------------------------------
			TheTimer = new Timer();
			TheTimer.Interval = 1000;
			TheTimer.Tick +=new EventHandler(TheTimer_Tick);
            //--------------------------------
		}
		#endregion

		#region Disposer
		/// <summary>
		/// Clean up any resources being used.
		/// </summary>
		protected override void Dispose( bool disposing )
		{
			if( disposing )
			{
				if( components != null )
					components.Dispose();
			}
			base.Dispose( disposing );
		}

		#endregion

		#region The tick of the timer
		/// <summary>
		/// Does various things at every timer tick
		/// </summary>
		/// <param name="Sender"></param>
		/// <param name="e"></param>
		public void TheTimer_Tick(object Sender,EventArgs e)
		{
			// Move the current block down every tick till it cant
			TheBlock.Move(MoveTo.Down);

			if(NewBlockReqd)
			{
                // To keep track of the lines in this move
				int linesDoneJustNow=0;
                // To keep track whether we need to refresh the screen
				bool refreshReqd = false;

				// Checking if a row is completed in the move just done and updating reqd if yes
				for(int row=0; row<Rows; row ++)
				{
					if(LineCompleted(row)) // If the line is completed
					{
                        // Clear the line just completed from the screen
						ClearLine(row);
                        // Keep track of the lines completed with this move
						linesDoneJustNow++;
                        // Keep track of the total lines completed 
						Lines++;
                        // Get squares above the line just done to move lower
						MoveSquaresDown(row);
                        // Making note that refresh function needs a call
						refreshReqd = true;
					}
				}

				// If done, refresh will be required
				if(refreshReqd)
					RefreshSquares();

				#region Score and Level updater
				// Updating score depending on number of lines done
				switch(linesDoneJustNow)
				{
					case 1:
						Score+=1;
						break;
					case 2:
						Score+=3;
						break;
					case 3:
						Score+=6;
						break;
					case 4:
						Score+=10;
						break;
				}

                // Update the level
				UpdateLevel();
				#endregion

				if(NewBlockMade !=null)
					NewBlockMade(this, new EventArgs());

				// Initializing the block and the one in queue
				InitializeBlock();
				InitializeNextBlock();				
			}
		}		
		#endregion

		#region Function to update the level
		/// <summary>
		/// Change the level after every 30 lines of completion
		/// </summary>
		private void UpdateLevel()
		{
			if(Lines<Level*10) // Change the level after every 30 lines of completion
				return;

			if(Level<10)
			{
				Level++;
				TheTimer.Interval -=100;
			}
		}
		#endregion

		#region Input from Keyboard
        /// <summary>
        /// Reading the input and moving the block accordingly
        /// </summary>
        /// <param name="keyData">Key pressed</param>
        /// <returns> Returns true by default</returns>
		protected override bool ProcessDialogKey(Keys keyData)
		{
			// If the game isnt started and user presses ALT to go to the menu, theres an 
			// error. This is to take care of that
			if(!GameStart)
				return base.ProcessDialogChar((char)(int) keyData);

			if(keyData == Keys.P)
			{
                GamePause();
				return true;
			}
			
            // Default movement of the block is downwards
			MoveTo direction= MoveTo.Down;

            // Check what key was pressed and decide the direction of the move accordingly
			switch(keyData)
			{
				case Keys.Up:
					direction = MoveTo.Rotate;
					break;
				case Keys.Down:
					direction = MoveTo.Down;
					break;
				case Keys.Left:
					direction = MoveTo.Left;
					break;
				case Keys.Right:
					direction = MoveTo.Right;
					break;		
			}

            // Move the block in the required direction
			TheBlock.Move(direction);

			return true;
		}
		#endregion

        #region Override the base functions
        /// <summary>
		/// Things to do when drawing the window and its elements
		/// </summary>
		/// <param name="pe"></param>
		protected override void OnPaint(PaintEventArgs pe)
		{
			foreach( UnitSquare square in TheSquares)
			{
				if(!pe.Graphics.ClipBounds.IntersectsWith(square.TheSquare))
					continue;
				if(square.ColorFlag)
					square.Render(pe.Graphics);
				else
					pe.Graphics.FillRectangle(new SolidBrush(BackColor), square.TheSquare);
			}
		}

		protected override void OnResize(EventArgs e)
		{
			UpdateSize();
			base.OnResize (e);
        }
        #endregion

        #region Public Functions
        /// <summary>
        /// Initialize a new game instance
        /// </summary>
        public void InitializeNewGame()
        {
            // Initialize the flags
            //--------------------------------
            GameStart = true;			// Game Start flag
            GameEnd = false;			// Game End flag
            //--------------------------------

            // Initialize the scores and levels
            //--------------------------------
            Level = 1;					// Starting level
            Score = 0;					// Starting score
            Lines = 0;					// Starting number of lines completed
            StatusString = "P = Pause";
            //--------------------------------

            // Initialize the squares to draw
            //--------------------------------
            InitializeSquares();		// The actual game area initialization
            //InitializeNextBlock();		// Start with the 1st block in the game
            NextBlock = (new Random()).Next(0,7);
            InitializeBlock();
            InitializeNextBlock();
            //--------------------------------

            // Start the timer
            TheTimer.Start();

            // Fire the StartGame event
            if (StartGame != null)
                StartGame(this, new EventArgs());
        }

        /// <summary>
		/// Render those squares that need to be colored
		/// </summary>
		public void RefreshSquares()
		{
            // The number of squares to draw
			int squaresInList = 0;

            // The rectangle that will be constructed from all the squares
			Rectangle refreshList = new Rectangle();

			// Form the rectangle that needs to be repainted
			foreach(UnitSquare square in SquaresToInvalidate)
			{
                // Check if we have a bigger rectangle constructed already
				if(squaresInList==0)
					refreshList = square.TheSquare;
				else
                    // Use the Amzing property to union the square with the rectangle we have constructed till now
					refreshList = Rectangle.Union(refreshList, square.TheSquare); 

                // Keep a track of the number of rectangles we have in the list
				squaresInList++;
			}

			// Invalidate the rectangle just formed
			Invalidate(refreshList);

			// Get the squares to invalid list ready for next cycle
			SquaresToInvalidate.Clear();
        }

        /// <summary>
        /// Function updates the width and height according to the number of rows n columns
        /// to be displayed
        /// </summary>
        public void UpdateSize()
        {
            Width = Columns * UnitSquareWidth;
            Height = Rows * UnitSquareWidth;
            InitializeSquares();
        }

        /// <summary>
        /// Handles the pause/play 
        /// </summary>
        public void GamePause()
        {
            if (GamePaused)
            {
                GamePaused = false;
                StatusString = "P = Pause";
                TheTimer.Enabled = true;
            }
            else
            {
                GamePaused = true;
                StatusString = "P = Play";
                TheTimer.Enabled = false;
            }
        }

        #endregion

        #region Private Functions

        /// <summary>
        /// Initiazlies theSquares on the panel to be ready to be manipulated to draw etc.
        /// </summary>
        private void InitializeSquares()
        {
            // Create a new list of the Squares
            TheSquares = new UnitSquare[Rows * Columns];

            int squareIndex = 0;

            // Loop over the number of rows and columns we have and create a square for each count
            for (int nRows = 0; nRows < Rows * UnitSquareWidth; nRows += UnitSquareWidth)
            {
                for (int nCols = 0; nCols < Columns * UnitSquareWidth; nCols += UnitSquareWidth)
                {
                    TheSquares[squareIndex] = new UnitSquare(this);
                    TheSquares[squareIndex++].TheSquare = new Rectangle(nCols, nRows, UnitSquareWidth, UnitSquareWidth);
                }
            }
        }

        /// <summary>
        /// Randomly selects a new block from the available ones
        /// </summary>
        private void InitializeNextBlock()
        {
            NextBlock = (new Random()).Next(0,7);

            // Fire the NextBlockMade event
            if (NextBlockMade != null)
                NextBlockMade(this, new EventArgs());
        }

        /// <summary>
        /// Initialize the current block
        /// </summary>
        private void InitializeBlock()
        {
            switch (NextBlock)	// What the block is going to be
            {
                case 0:
                    TheBlock = new SquareBlock(this);
                    break;
                case 1:
                    TheBlock = new LShapedBlock(this);
                    break;
                case 2:
                    TheBlock = new GammaShapedBlock(this);
                    break;
                case 3:
                    TheBlock = new IShapedBlock(this);
                    break;
                case 4:
                    TheBlock = new SBlock(this);
                    break;
                case 5:
                    TheBlock = new ZBlock(this);
                    break;
                case 6:
                    TheBlock = new TBlock(this);
                    break;
                default:
                    TheBlock = null;
                    break;
            }

            if (!TheBlock.IsPossibleToDraw()) // Check if the block can be actually drawn in the panel
            {
                GameEnd = true;		// Game ends if we cant draw the block
                ToDoAtGameEnd();	// things to do at the end of the game
                return;
            }

            // Render it on the screen
            TheBlock.RenderBlock();

            // Refresh the squares
            RefreshSquares();

            // Dont need another one right away
            NewBlockReqd = false;
        }

        /// <summary>
        /// Check if the asked row is completed or not
        /// </summary>
        /// <param name="row">Row number to check</param>
        /// <returns>True if done else false</returns>
        private bool LineCompleted(int row)
        {
            for (int i = row * Columns; i < (row + 1) * Columns; i++) // looping thr squares in the row
                if (!TheSquares[i].ColorFlag)					// check if filled
                    return false;								// if not, row is not done
            return true;
        }

        /// <summary>
        /// Clearing a line of squares in the world
        /// </summary>
        /// <param name="row">Row number to clear</param>
        private void ClearLine(int row)
        {
            for (int i = row * Columns; i < (row + 1) * Columns; i++) // looping thr the sqrs in the row
            {
                TheSquares[i].ColorFlag = false;				// Marking not filed
                SquaresToInvalidate.Add(TheSquares[i]);			// Invalidating for next rendering
            }
        }

        /// <summary>
        /// Move all the squares from above the requested line below. This takes care of the 
        /// situation when a line between is done and the blocks need to be moved down
        /// </summary>
        /// <param name="row">The row that is affected and moved into to start with</param>
        private void MoveSquaresDown(int row)
        {
            for (int i = (row + 1) * Columns - 1; i >= 0; i--)		// Looping thr all squares above line
                if (TheSquares[i].ColorFlag)						// if there exists something
                {
                    TheSquares[i].ColorFlag = false;			// remove it from there
                    TheSquares[i + Columns].ColorFlag = true;		// place it in line below
                    SquaresToInvalidate.Add(TheSquares[i]);		// invalidate the operation
                }
        }

        /// <summary>
		/// What the name of the function suggests
		/// </summary>
		public void ToDoAtGameEnd()
		{
			TheTimer.Stop();			// Stop the timer
			if(GameEnd)
			{
				DialogResult userReply = MessageBox.Show("Game Over","SiRtEt",MessageBoxButtons.OK);
			}
			
			if(EndGame != null)			// An event handler takes care of score report etc.
				EndGame(this, new EventArgs());

			InitializeSquares();
			Invalidate();
			SquaresToInvalidate.Clear();
        }
        #endregion

        #region Component Designer generated code

        /// <summary>
        /// Required designer variable.
        /// </summary>
        private System.ComponentModel.Container components = null;

		/// <summary>
		/// Required method for Designer support - do not modify 
		/// the contents of this method with the code editor.
		/// </summary>
		private void InitializeComponent()
		{
			components = new System.ComponentModel.Container();
		}
		#endregion
	}
}
